写作不易,未经作者允许禁止以任何形式转载!
如果觉得文章不错,欢迎关注、点赞和分享!

认识 Loader

Loader 可以用于对模块的源代码进行转换;

在加载这个模块时,webpack 其实并不知道如何对其进行加载,我们必须定制对应的 loader 来完成这个功能。

loader 配置方式

  • 内联方式:import "css-loader!../css/index.css"; loader 和文件路径用!分隔

  • 配置方式:webpack.config.js

  • 配置方式表示的意思是在我们的 webpack.config.js 文件中写明配置信息

    • module.rules 中允许我们配置多个 loader(因为我们也会继续使用其他 Loader,来完成其他文件的加载)
    • 这种方式可以更好地标识 loader 配置,也方便后期的维护,同时也让你对各个 Loader 有一个全局的概览;
  • module.rules 的配置如下

  • rules 属性对应的值是一个数组:[Rule]

  • 数组中存放的是一个个 Rule,Rule 是一个对象,对象中可以设置多个属性

    • test 属性:用于多 resource(资源)文件名进行匹配,通常会设置成正则表达式;
    • use 属性:对应的值是一个数组[useEntry]
      • 执行顺序从后往前,比如解析 css,css-loader 应在 style-loader 后面。
      • UseEntry 是一个对象,可以通过对象的属性来设置一些其他属性
        • loader:必须有一个 loader 属性,对应的值是一个字符串
        • options:可选的属性,值是一个字符串或者对象,值会被传入到 loader 中;
        • query:目前已被 options 替代
      • 传递字符串(如:use:[‘style-loader’])是 loader 属性的简写(如:use:[{loader:‘style-loader’}])

常见 Loader

CSS loader

  • 我们可以将 css 文件也看成是一个模块,我们是通过 import 来加载这个模块的

  • 那么需要一个什么样的 loader 呢

    • 对于加载 css 文件来说,我们需要一个可以读取 css 文件的 loader

    • 最常用的是 css-loader

    • 只负责解析 css 文件,不会作用于页面,此外还需要 style-loader 作用于页面

      npm i style-loader --save

  • css-loader 的安装:

    npm install css-loader --save

  • 以下为一个 css-loader 的配置文件

const path = require('path');

module.exports = {
entry: './src/js/main.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'build'),
},
module: {
rules: [
{
test: /\.css$/, //资源文件匹配正则表达式
use: [
{
loader: 'style-loader',
options: {},
},
// 简写:
// “css-loader”
{
// 完整写法
loader: 'css-loader', // 对应的loader
options: {},
},
],
},
],
},
};
  • less 同理

    npm install less-loader less --save安装这两个插件后

  • 依次在 use 数组内写入 style-loader、css-loader、less-loader

浏览器兼容性

  • 如果在 css 兼容性和 js 兼容性下共享我们的配置兼容性条件呢

    • 就是当我么们设置了一个条件:>1% last 2 version Firefox、Chrome… not dead 都是可选项,多个选项以,分隔为 || 条件;and 为&&关系;可用 not 条件
    • 意思为 js、css 兼容市场占有率大于 1%的 Firefox、Chrome 浏览器和最新的两个版本,并且 24 个月内有官方支持和更新的浏览器(dead 过滤条件)
  • 在根目录.browserlistrc 文件编写条件

  • 通过可实现对市场占有比率的要去,版本要求等进行配置,适配浏览器

  • 所用工具:

    • 数据来源:browserlist,浏览器市场占有率,精确到每个版本
    • 处理插件:autoprefixer、babel、postcss-preset-env 等

认识 PostCSS 工具

  • 什么是 PostCSS 工具呢?

    • PostCSS 是一个通过 JavaScript 来转换样式的工具
    • 这个工具可以帮助我们进行一些 CSS 样式的转换和适配,比如自动添加浏览器前缀、css 样式的充值;
    • 但是实现这些功能,我们需要借助于 PostCSS 插件
  • 如何使用 PostCSS

  1. 查找 PostCSS 在构建工具中的扩展,比如 webpack 中的 postcss-loader;
  2. 选择可以添加你需要的 PostCss 相关插件

手动使用 PostCSS

使用 postcss-cli 操作,需要用到 autoprefixer 插件

  1. npm install postcss postcss-cli --save
  2. npm install autoprefixer --save
  3. npx postcss --use autoprefixer -o '输出路径' '输出路径' ,例如:
    1. npx postcss --use autoprefixer -o ./src/css/result.css ./src/css/user.css

Webpack 配置文件使用 PostCSS

  1. 安装 postcss-loader 和 autoprefixer
    • npm install postcss-loader autoprefixer --save
  2. 编写配置文件
    • css-loader 处理前先用 postcss-loader 处理
    • 在 options 的 postcssOptions 中,配置 plugins,使用 autoprefixer
{
test: /\.css$/, //资源文件匹配正则表达式
use: [
{
loader: "style-loader",
options: {},
},
// 简写:
// “css-loader”
{
// 完整写法
loader: "css-loader", // 对应的loader
options: {},
},
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [require("autoprefixer")],
},
},
},
],
},

postcss-preset-env

  • 事实上,在配置 postcss-loader 时,我们配置插件并不需要使用 autoprefixer。

  • 我们可以使用另外一个插件:postcss-preset-env

    • postcss-preset-env 也是一个 postcss 的插件;

    • 它可以帮助我们将一些现代的 CSS 特性,转成大多数浏览器认识的 CSS,并且会根据目标浏览器或者运行时环 境添加所需的 polyfill;

      • 例如八位的十六进制颜色会帮我们转换成 RGBA
    • 也包括会自动帮助我们添加 autoprefixer(所以相当于已经内置了 autoprefixer);

    • 首先,我们需要安装 postcss-preset-env:

      npm install postcss-preset-env --save

    • 之后,我们直接修改掉之前的 autoprefixer 即可:

  • 配置简化

    • 在 use 中只写 postcss-loader
    • 在项目根目录建立 postcss.config.js,写入配置

webpack.config.js

const path = require('path');

module.exports = {
entry: './src/js/main.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'build'),
},
module: {
rules: [
{
test: /\.css$/, //资源文件匹配正则表达式
use: [
{
loader: 'style-loader',
options: {},
},
// 简写:
// “css-loader”
{
// 完整写法
loader: 'css-loader', // 对应的loader
options: {},
},
'postcss-loader',
],
},
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'postcss-loader', 'less-loader'],
},
],
},
};

postcss.config.js

module.exports = {
plugins: [
// 引入
// require("postcss-preset-env")
//简写
'postcss-preset-env',
],
};

效果

Before

:fullscreen {
}

.content {
user-select: none;
transition: all 2s ease;
}

After

:-webkit-full-screen {
}

:-ms-fullscreen {
}

:fullscreen {
}

.content {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
transition: all 2s ease;
}
/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInVzZXIuY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztBQUVBOztBQUZBOztBQUVBOztBQUZBOztBQUVBOztBQUVBO0lBQ0kseUJBQWlCO09BQWpCLHNCQUFpQjtRQUFqQixxQkFBaUI7WUFBakIsaUJBQWlCO0lBQ2pCLHVCQUF1QjtBQUMzQiIsImZpbGUiOiJyZXN1bHQuY3NzIiwic291cmNlc0NvbnRlbnQiOlsiOmZ1bGxzY3JlZW57XG5cbn1cblxuLmNvbnRlbnR7XG4gICAgdXNlci1zZWxlY3Q6IG5vbmU7XG4gICAgdHJhbnNpdGlvbjogYWxsIDJzIGVhc2U7XG59Il19 */

importLoaders

  • 正常 css 文件解析遇到@import,不会再从 use 数组最后一个 loader 开始重新解析

  • 通过设置 importLoaders 可以实现递归解析

  • 填写的数字为在 use 数组中往后的 loader 的数量

  • 例如:

const path = require('path');

module.exports = {
entry: './src/js/main.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'build'),
},
module: {
rules: [
{
test: /\.css$/, //资源文件匹配正则表达式
use: [
{
loader: 'style-loader',
options: {},
},
// 简写:
// “css-loader”
{
// 完整写法
loader: 'css-loader', // 对应的loader
options: {
importLoaders: 1,
},
},
'postcss-loader',
],
},
{
test: /\.less$/,
use: [
'style-loader',
{
// 完整写法
loader: 'css-loader', // 对应的loader
options: {
importLoaders: 2,
},
},
'postcss-loader',
'less-loader',
],
},
],
},
};

File Loader

希望解析、读取文件,例如图片

  1. 安装 file-loader
    • npm install --save file-loader
  2. 编写配置文件

rule:

  • options 解释:
    • name:输出名称,[name]名称引用,.字符,[hash:6]哈希信息截取 6 位,[ext]后缀名
    • ouputPath:输出的文件夹,也可以在文件名前加 img/
{
test: /\.(png|jpe?g|gif|svg)$/,
use: [
{
loader:"file-loader",
options:{
name:"[name].[hash:6].[ext]",
outputPath:"img"
// limit(url-loader)
}
}
],
},
  • 和 file-loader 有同样功能的还有 url-loader
    • 不同的是,它不是打包,而是把图片转为 BASE64 编码。
    • 可以通过 use 对象的 options 下写入 limit 属性,限制大小,对小图片进行 BASE4 编码,减少请求数。

Asset module type

  • 我们当前使用的 webpack 版本是 webpack5:

    • 在 webpack5 之前,加载这些资源我们需要使用一些 loader,比如 raw-loader 、url-loader、file-loader;
    • 在 webpack5 之后,我们可以直接使用资源模块类型(asset module type),来替代上面的这些 loader;
  • 资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换所有这些 loader:

    • asset/resource 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现;
    • asset/inline 导出一个资源的 data URI。之前通过使用 url-loader 实现;
    • asset/source 导出资源的源代码。之前通过使用 raw-loader 实现;
    • asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源
  • 比如加载图片,我们可以使用下面的方式:

// 打包图片
{
test: /\.(png|jpe?g|gif|svg)$/,
type: "asset/resource",
generator: {
filename: "img/[name].[hash:6][ext]",
},
},
// BASE64编码并限制大小
{
test: /\.(png|jpe?g|gif|svg)$/,
type: "asset/inline",
parser: {
dataUrlCondition: {
maxSize: 100 * 1024,
},
},
},
// 混合使用,限制大小,判断打包方式
{
test: /\.(png|jpe?g|gif|svg)$/,
type: "asset",
generator: {
filename: "img/[name].[hash:6][ext]",
},
parser: {
dataUrlCondition: {
maxSize: 100 * 1024,
},
},
},
  • 但是,如何可以自定义文件的输出路径和文件名呢?
    • 方式一:修改 output,添加 assetModuleFilename 属性;
    • 方式二:在 Rule 中,添加一个 generator 属性,并且设置 filename;
// 方式一 ouput对象
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "build"),
assetModuleFilename: "img/[name].[hash:6][ext]",
},
// 方式二 rule对象(推荐)
{
test: /\.(png|jpe?g|gif|svg)$/,
type: "asset/resource",
generator: {
filename: "img/[name].[hash:6][ext]",
},
}

加载字体文件

rule 对象:

{
test: /\.ttf|eot|woff2?$/i,
type: "asset/resource",
generator: {
filename: "font/[name].[hash:6][ext]",
},
},

自定义 Loader

Loader 是用于对模块的源代码进行转换处理,之前已经使用过很多 Loader,例如 css-loader、style-loader、babel-loader 等

  • Loader 本质是一个导出为函数的 JavaScript 模块

  • Loader runner 库会调用这个函数,然后将上一个 loader 产生的结果或者资源文件传入进去。

  • 编写一个自定义 Loader 会接收三个参数

    • content:资源文件参数
    • map:sourcemap 相关数据
    • meta:一些元数据
  • 注:传入的路径是和 content 有关系的

    • webpack.config.js
{
context: path.resolve(__dirname, "../"),
entry: "./src/main.js",
output: {
path:path.resolve(__dirname,"../build"),
filename:"bundle.js",
},
module: {
rules:[
{
test:/\.js$/i,
use:[
"./loaders/demoLoader.js"
]
}
]
}
}
  • 如果希望直接去加载 loader 文件夹,可以配置 resoveLoader 属性
module: {
rules:[
{
test:/\.js$/i,
use:[
"demoLoader"
]
}
]
},
resolveLoader:{
modules:["./loaders", "node_modules"]
}
  • 多个 Loader 使用执行顺序
    • 从后向前,从右向左
rules: [
{
test: /\.js$/i,
use: ['demoLoader1', 'demoLoader2', 'demoLoader3'],
},
];
  • 结果

image.png

Pitch-Loader 和 enforce

事实上还有另一种 Loader,称之为 PitchLoader

  • 其实这也是为什么 loader 的执行顺序是相反的

    • run-loader 先优先执行 PitchLoader,在执行 PitchLoader 时进行 loaderIndex++;
    • run-loader 之后会执行 NormalLoader,在执行 NormalLoader 时进行 loaderIndex–;
  • 那么,怎么改变他们的执行顺序?

    • 可以拆分成多个 Rule 对象,通过 enforce 来改变它们的顺序
    • enforce 是 rule 对象的一个属性。
  • enforce 一共有四种方式

    • 默认所有 loader 都是 normal
    • 在行内设置的 loader 都是 inline
    • 也可以通过 enforce 设置 pre 和 post
  • 在 Pitch 和 Normal 它们的执行顺序分别是

    • post、inline、normal、pre
    • pre、normal、inline、post

同步的 Loader

  • 什么是同步 loader?

    • 默认创建的 Loader 就是同步的 Loader
    • 这个 Loader 必须通过 return 或者 this.callback 来返回结果,交给下一个 loader 来处理
    • 通常在有错误的情况下,会使用 this.callback
  • this.callback 用法如下

    • 第一个参数为 ERR 或者 null
    • 第二个参数是 string 或者 buffer
    • loader.js
module.exports = function (content) {
console.log('loader', content);
this.callback(null, content);
};

异步的 Loader

  • 什么是异步的 Loader

    • 有时候使用 Loader 进行一些异步的操作
    • 我们希望在异步操作完成之后,再返回这个 loader 处理的结果
    • 这个时候就要使用异步的 Loader 了
  • loader-runner 已经在执行 loader 时给我们提供了方法,让 loader 变成一个异步的 loader

  • loader.js

module.exports = function (content) {
const callback = this.async();
setTimeout(() => {
console.log('async loader', content);
callback(null, content);
});
};

传入和获取参数

  • 在使用 loader 时传入参数,

  • 可以通过 webpack 官方提供的一个解析库 loader-utils

    • npm i loader-utils
  • webpack.config.js

{
test:/\.js$/i,
use:[
"syncLoader",
"asyncLoader",
{
loader:"optionLoader",
options:{
type:"options",
desc:"demo"
}
}
]
}
  • optionLoader.js
const { getOptions } = require('loader-utils');

module.exports = function (content) {
const callback = this.async();

const options = getOptions(this);

setTimeout(() => {
console.log('asyncLoader', content, options);
callback(null, content);
});
};

// asyncLoader console.log("main.js") { type: 'options', desc: 'demo' }

参数校验

  • 可以通过 wepack 官方提供的校验库 schema-utils

    • npm i schema-utils
  • schema.json(校验规则)

{
"type": "objcet",
"properties": {
"type": {
"type": "string",
"description": "请输入正确的参数类型"
},
"desc": {
"type": "string",
"description": "描述必须为字符串类型"
}
},
"additionalProperties": true
}
  • loader.js
const { getOptions } = require('loader-utils');
const { validate } = require('schema-utils');
const loaderSchema = require('../schema/schema.json');

module.exports = function (content) {
const callback = this.async();

const options = getOptions(this);

validate(loaderSchema, options);

setTimeout(() => {
console.log('asyncLoader', content, options);
callback(null, content);
});
};

babel-loader 案例

  • 我们知道 babel-loader 可以进行 JS 代码转换

  • 接下来尝试定义一个自己的 babel-loader

  • myBabelLoader.js

const babel = require('@babel/core');
const { getOptions } = require('loader-utils');
const { validate } = require('schema-utils');
const babelSchema = require('../schema/babel.json');

module.exports = function (content) {
const callback = this.async();

const option = getOptions(this);

validate(babelSchema, option);

babel.transform(content, option, (err, res) => {
if (err) {
callback(err);
} else {
callback(null, res.code);
}
});
};
  • babelSchema.json
{
"type": "object",
"properties": {
"presets": {
"type": "array"
}
},
"additinalProperties": true
}
  • webapck.config.js
module: {
rules:[
{
test:/\.js$/i,
use:[
"syncLoader",
"asyncLoader",
{
loader:"optionLoader",
options:{
type:"options",
desc:"demo"
}
},
{
loader:"myBabelLoader",
options: {
presets:["@babel/preset-env"]
}
}
]
}
]
},

自定义 md-loader

  • 借助 marked 和 highlight 库实现 markdownLoader

    • npm i marked highlight.js
  • webpack.config.js

    • 配置 css 加载和 md 加载规则
rules: [
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
{
test: /\.md$/i,
use: ['mdLoader'],
},
];
  • mdLoader.js
    • 使用 marked 和 highlight 处理 content,并返回模块化代码
const marked = require('marked');
const hljs = require('highlight.js');

module.exports = function (content) {
marked.setOptions({
highlight: function (code, lang) {
return hljs.highlight(lang, code).value;
},
});

const htmlContent = marked(content);
const innerContent = '`' + htmlContent + '`';
const moduleCode = `var code=${innerContent};export default code;`;

console.log(moduleCode);

return moduleCode;
};
  • main.js
    • 同时引入 css 文件
import md from './index.md';
import 'highlight.js/styles/default.css';

console.log('main.js');

const ele = document.createElement('div');
ele.innerHTML = md;

document.body.appendChild(ele);
  • 效果

image-20210502234524690

img